using System;
using System.IO;
using System.Web.Caching;
using System.Xml;
using System.Web.UI;
using System.Xml.Xsl;
using System.Diagnostics;
using Microsoft.SharePoint;
using System.Web;
using CKS.EBE.API;
using Microsoft.SharePoint.Utilities;
using Microsoft.SharePoint.WebControls;
using System.Globalization;

namespace CKS.EBE.WebControls
{
	public class XslTransformation : SPControl
	{
		private string _XslName = "";
		private int _ItemsToDisplay = 10;
		private int _CacheTimeout = 2;
		private XsltArgumentList _XslArguments;
		private string _CacheKey;
		private string _CacheID = "";
		private bool _CachePerPage = true;
		private static readonly object _Cache = new object();

		public bool TranformErrorXml { get; set; }

		public bool CacheHtml { get; set; }

		public int ItemsToDisplay
		{
			get { return _ItemsToDisplay; }
			set { _ItemsToDisplay = value; }
		}

		public int CacheTimeout
		{
			get { return _CacheTimeout; }
			set { _CacheTimeout = value; }
		}

		public string CacheKey
		{
			get { return CacheKeyPrefix + _CacheKey; }
			set { _CacheKey = value; }
		}

		public string CacheID
		{
			get { return _CacheID; }
			set { _CacheID = value; }
		}

		public bool CachePerPage
		{
			get { return _CachePerPage; }
			set { _CachePerPage = value; }
		}

		private string CacheKeyPrefix
		{
			get
			{
				string sKey = "_ebeBlog_" + HttpContext.Current.User.Identity.IsAuthenticated;

				if (!BlogContext.Current.Settings.DisableConvertClientDates)
					sKey += "~" + BlogContext.Current.ClientTimeZoneOffsetFromWeb + "~";

				if (CachePerPage)
					sKey += HttpContext.Current.Request.Url.ToString();

				return sKey + BlogContext.Current.BlogTheme + _CacheID;
			}
		}

		private static bool IsCachingEnabled
		{
			get
			{
				return BlogContext.Current.Settings.CachingEnabled;
			}
		}

		private static string HtmlCacheKeySuffix
		{
			get
			{
				return "_Html_" + BlogContext.Current.BlogTheme;
			}
		}

		public string XslName
		{
			get { return _XslName; }
			set { _XslName = value; }
		}

		public XmlDocument Document { get; set; }

		private string CachedHtml
		{
			get
			{
				if (!IsCachingEnabled || !CacheHtml) return null;

				return (string)HttpContext.Current.Cache[CacheKey + HtmlCacheKeySuffix];
			}
			set
			{
				if (!IsHtmlCached && IsCachingEnabled && CacheHtml)
					HttpContext.Current.Cache.Add(CacheKey + HtmlCacheKeySuffix, value, null, DateTime.Now.AddMinutes(CacheTimeout), TimeSpan.Zero, CacheItemPriority.AboveNormal, null);
			}
		}

		private bool IsHtmlCached
		{
			get
			{
				return HttpContext.Current.Cache[CacheKey + HtmlCacheKeySuffix] != null;
			}
		}

		protected override void Render(HtmlTextWriter writer)
		{
			try
			{
				if (CacheHtml && IsCachingEnabled && IsHtmlCached)
				{
					writer.Write(CachedHtml);
					return;
				}

				XmlDocument oData = Document;

				if (oData == null) { oData = new XmlDocument(); oData.LoadXml("<Error>Document is null</Error>"); }

				if (oData.DocumentElement.Name == "Error" && !TranformErrorXml)
				{
					writer.Write(string.Format("<div style='color:red'>Failed to render control: {0}</div>", oData.DocumentElement.InnerText));
					return;
				}

				if (!BlogContext.Current.Settings.DisableConvertClientDates)
					oData = ConvertDates(oData);

				XslCompiledTransform oTransform = LoadStylesheet();
				if (oTransform == null) return;

				HtmlTextWriter tw = writer;

				if (!IsHtmlCached && IsCachingEnabled && CacheHtml)
				{
					tw = new HtmlTextWriter(new StringWriter());
				}

				oTransform.Transform(oData, XslArguments, tw);

				if (!IsHtmlCached && IsCachingEnabled && CacheHtml)
				{
					CachedHtml = tw.InnerWriter.ToString();
					writer.Write(CachedHtml);
				}
			}
			catch (Exception ex)
			{
				Trace.Write(ex);
				writer.Write(string.Format("<div style='color:red'>Failed to render control: {0}</div>", ex.Message));
			}
		}

		private static XmlDocument ConvertDates(XmlDocument oData)
		{
			if (oData == null) throw new ArgumentNullException("oData");
			XmlElement documentElement = oData.DocumentElement;
			if (documentElement == null) return new XmlDocument();

			if (documentElement.GetAttribute("datesConverted") == "true") return oData;

			Trace.WriteLine("Converting client dates: Offset=" + BlogContext.Current.ClientTimeZoneOffsetFromWeb);

			XmlDocument oNew = new XmlDocument();
			oNew.LoadXml(oData.OuterXml);

			XmlNodeList oList = oNew.DocumentElement.SelectNodes("//Created | //PublishedDate | //Modified");
			foreach (XmlElement oDate in oList)
			{
				oDate.InnerText = XslMethods.clientDateTime(oDate.InnerText);
			}

			oNew.DocumentElement.SetAttribute("datesConverted", "true");

			return oNew;
		}

		private XslCompiledTransform LoadStylesheet()
		{
			SPWeb currentWeb = SPContext.Current.Web;
			BlogContext blogContext = BlogContext.Current;

			string xslUrl;
			// only deal with the cache if xsl caching is not disabled
			if (blogContext.Settings.XslCacheDependencyDisabled)
			{
				return LoadTransformation(currentWeb, out xslUrl);
			}

			// XslTransformation needs to be cached
			lock (_Cache)
			{
				string cacheKey = string.Format("CksEbeXsl_{0}__{1}__{2}", currentWeb.Url, blogContext.BlogTheme, XslName);
				object oCachedXsl = HttpRuntime.Cache.Get(cacheKey);
				// non admins or contributors will use a cached output if it exists
				if (oCachedXsl != null && !(currentWeb.UserIsWebAdmin || blogContext.IsBlogContributor))
				{
					// we don't need to build the page again. Return it from the cache
					return (XslCompiledTransform) oCachedXsl;
				}

				try
				{
					var transformation = LoadTransformation(currentWeb, out xslUrl);
					if (blogContext.Settings.XslCacheDependencyDisabled)
					{
						// if xsl caching is deactivated, return the generated transformation
						return transformation;
					}

					int cacheDuration = blogContext.Settings.PageOutputCacheTimeout;
					// only cache the current page if the user is not an admin or contributor
					if (!currentWeb.UserIsWebAdmin && !blogContext.IsBlogContributor && cacheDuration > 0)
					{
						// cacheDuration was 1440 (one day), was key instead of cacheKey
						//HttpRuntime.Cache.Add(cacheKey, transformation, new SPFileCacheDependency(SPContext.Current.Site.Url, xslUrl), DateTime.Now.AddMinutes(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
						HttpRuntime.Cache.Add(cacheKey, transformation, null, DateTime.Now.AddMinutes(cacheDuration), Cache.NoSlidingExpiration, CacheItemPriority.Normal, null);
					}
					return transformation;
				}
				catch (Exception ex)
				{
					Trace.WriteLine(string.Format("Failed to load stylesheet: {0} ({1})", XslName, currentWeb.Url));
					Trace.Write(ex);
					return null;
				}
			}
		}

		/// <summary>
		/// create a transformation
		/// </summary>
		/// <param name="currentWeb"></param>
		/// <param name="xslUrl"></param>
		/// <returns></returns>
		private XslCompiledTransform LoadTransformation(SPWeb currentWeb, out string xslUrl)
		{
			var xslDocument = new XmlDocument();
			xslUrl = GetXslUrl(currentWeb);
			Trace.WriteLine("Loading..." + xslUrl);
			xslDocument.LoadXml(currentWeb.GetFileAsString(xslUrl));

			var transformation = new XslCompiledTransform();
			transformation.Load(xslDocument);
			return transformation;
		}

		private string GetXslUrl(SPWeb currentWeb)
		{
			using (var site = new SPSite(currentWeb.Url))
			using (var rootWeb = site.RootWeb)
			{
				string xslUrl = string.Format("/Themes/{0}/{1}", BlogContext.Current.BlogTheme, XslName);
				return !BlogContext.Current.Settings.UseSiteCollectionThemes ? SPUrlUtility.CombineUrl(currentWeb.Url, xslUrl) : SPUrlUtility.CombineUrl(rootWeb.Url, xslUrl);
			}
		}

		public XsltArgumentList XslArguments
		{
			get
			{
				if (_XslArguments != null) return _XslArguments;

				HttpRequest httpRequest = HttpContext.Current.Request;
				SPContext context = SPContext.Current;
				SPSite site = context.Site;
				SPWeb web = context.Web;
				BlogContext oContext = BlogContext.Current;

				_XslArguments = new XsltArgumentList();

				try
				{
					_XslArguments.AddParam("RequestUrl", "", httpRequest.Url.ToString());
					_XslArguments.AddParam("RequestMethod", "", httpRequest.HttpMethod);
					if (httpRequest.UserAgent != null)
						_XslArguments.AddParam("RequestUserAgent", "", httpRequest.UserAgent);
				}
				catch (Exception ex)
				{
					Trace.WriteLine(ex);
				}

				try
				{
					_XslArguments.AddParam("IsBlogOwner", "", oContext.IsBlogOwner);
					_XslArguments.AddParam("IsBlogContributor", "", oContext.IsBlogContributor);
					_XslArguments.AddParam("ItemsToDisplay", "", ItemsToDisplay.ToString());
					_XslArguments.AddParam("Now", "", DateTime.Now.ToString());
					_XslArguments.AddParam("WebUrl", "", web.Url);
					_XslArguments.AddParam("SiteUrl", "", site.Url);
					_XslArguments.AddParam("ItemID", "", context.ItemId.ToString());
					_XslArguments.AddParam("NoPostInCategory", "", Localization.GetResource("NoPostInCategory"));
					// get datetime information from web.RegionalSettings
					_XslArguments.AddParam("Language", "", web.Locale.Name);
					// get datetime format pattern
					var lcid = web.RegionalSettings.LocaleId;
					var dateTimeFormat = new CultureInfo((int)lcid).DateTimeFormat;
					_XslArguments.AddParam("MonthDayPattern", "", dateTimeFormat.MonthDayPattern);          // MMMM dd
					_XslArguments.AddParam("ShortMonthDayPattern", "", dateTimeFormat.MonthDayPattern.Replace("MMMM", "MMM"));          // MMMM dd
					_XslArguments.AddParam("ShortDatePattern", "", dateTimeFormat.ShortDatePattern);        // M/d/yyyy
					_XslArguments.AddParam("ShortTimePattern", "", dateTimeFormat.ShortTimePattern);        // h:mm tt
					var fullDateTimePattern = dateTimeFormat.FullDateTimePattern.Replace(" " + dateTimeFormat.LongTimePattern, "");
					_XslArguments.AddParam("FullDateTimePattern", "", fullDateTimePattern);  // dddd, MMMM dd, yyyy

					if (context.ListId != Guid.Empty)
						_XslArguments.AddParam("ListID", "", context.ListId.ToString());

					if (context.List != null)
					{
						_XslArguments.AddParam("ListTitle", "", context.List.Title);
						_XslArguments.AddParam("ListUrl", "", context.List.RootFolder.Url);
					}

				}
				catch (Exception e) { Trace.WriteLine("Failed to set XSL transform params"); Trace.WriteLine(e); }


				try
				{
					_XslArguments.AddParam("AllowRSSFeeds", "", web.AllowRssFeeds);
					if (web.CurrentUser != null)
					{
						_XslArguments.AddParam("CurrentUserName", "", web.CurrentUser.Name);
						_XslArguments.AddParam("CurrentUserEmail", "", web.CurrentUser.Email);
					}
					_XslArguments.AddParam("BlogWebDescription", "", web.Description);
					_XslArguments.AddParam("BlogWebTitle", "", web.Title);
					_XslArguments.AddParam("IsWebAdmin", "", web.UserIsWebAdmin.ToString());
					_XslArguments.AddParam("IsSiteAdmin", "", web.UserIsSiteAdmin.ToString());

					_XslArguments.AddParam("EBEBlogTheme", "", oContext.BlogTheme ?? "");
				}
				catch (Exception e) { Trace.WriteLine("Failed to set XSL params"); Trace.WriteLine(e); }

				try
				{
					_XslArguments.AddParam("BlogContactEmail", "", oContext.Settings.ContactEmail ?? "");
					_XslArguments.AddParam("BlogPingBacksEnabled", "", oContext.Settings.PingbacksEnabled.ToString());

					_XslArguments.AddParam("SecureSystemPages", "", oContext.Settings.SecureSystemPages);
					_XslArguments.AddParam("ThemeSelectorEnabled", "", oContext.Settings.ThemeSelectorEnabled);
					_XslArguments.AddParam("TrimPostBodyOnLists", "", oContext.Settings.TrimPostBodyOnLists);

					_XslArguments.AddParam("BlogTransformParameter1", "", oContext.Settings.BlogTransformParameter1 ?? "");
					_XslArguments.AddParam("BlogTransformParameter2", "", oContext.Settings.BlogTransformParameter2 ?? "");
					_XslArguments.AddParam("BlogTransformParameter3", "", oContext.Settings.BlogTransformParameter3 ?? "");
				}
				catch (Exception e) { Trace.WriteLine("Failed to set params"); Trace.WriteLine(e); }

				try
				{
					_XslArguments.AddParam("CustomRssUrl", "", oContext.Settings.CustomRSSFeedUrl ?? "");
					_XslArguments.AddParam("CurrentUrl", "", httpRequest.Url.ToString());

					string sRelativeUrl;
					if (BlogContext.Current.Settings.UseSiteCollectionThemes)
					{
						sRelativeUrl = SPContext.Current.Site.RootWeb.ServerRelativeUrl;
					}
					else
					{
						sRelativeUrl = web.ServerRelativeUrl;
					}
					sRelativeUrl = sRelativeUrl.TrimEnd(new[] {'/'});
					_XslArguments.AddParam("RelativeUrl", "", sRelativeUrl);
				}
				catch (Exception e) { Trace.WriteLine("Error in XSL params"); Trace.WriteLine(e); }

				// Add the query parameters
				foreach (string sKey in httpRequest.QueryString.AllKeys)
				{
					_XslArguments.AddParam("QS" + sKey.ToLower(), "", httpRequest[sKey] ?? "");
				}

				if (!string.IsNullOrEmpty(httpRequest["id"]))
				{
					try
					{
						// test if we are in a posts list. we could be in a themed wiki
						SPList list = SPContext.Current.List;
						if (list != null && list.BaseTemplate == SPListTemplateType.Posts)
						{
							SPList oPosts = Helper.GetListByFolderName(web,
																					 Localization.GetListName(Localization.BlogList.Posts, web.Language));
							SPListItem oPost = oPosts.GetItemById(int.Parse(httpRequest["id"]));

							if (oPost != null)
							{
								_XslArguments.AddParam("PostTitle", "", oPost.Title);
								_XslArguments.AddParam("PostUniqueId", "", oPost.UniqueId);
								string sLink = XslMethods.createPostUrl(((DateTime)oPost["Created"]).ToString("yyyy-MM-dd HH:mm"),
																					 (string)oPost["BlogTitleForUrl"], oPost.ID.ToString());
								_XslArguments.AddParam("PostFullUrl", "", site.Url + sLink);
								_XslArguments.AddParam("IsTechnoratiEnabled", "", oContext.Settings.TechnoratiLinks);
							}
						}
					}
					catch { }
				}

				_XslArguments.AddExtensionObject("http://exslt.org/dates-and-times", new Mvp.Xml.Exslt.ExsltDatesAndTimes());
				_XslArguments.AddExtensionObject("http://exslt.org/strings", new Mvp.Xml.Exslt.ExsltStrings());
				_XslArguments.AddExtensionObject("http://exslt.org/sets", new Mvp.Xml.Exslt.ExsltSets());
				_XslArguments.AddExtensionObject("http://exslt.org/math", new Mvp.Xml.Exslt.ExsltMath());
				_XslArguments.AddExtensionObject("http://cks/ebemethods", new XslMethods());

				return _XslArguments;
			}
		}
	}
}